package matrix;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Stack;

import model.DoorModel;
import model.path.DijkstraResult;


public class NodeLinkMatrix extends SquareMatrix<GraphNode, ArrayList<GraphLink>> 
{
	public ArrayList<GraphLink> links;
	
	public NodeLinkMatrix(Collection<GraphNode> values) 
	{
		super(values, true);
		this.links = new ArrayList<GraphLink>();
		for(int row = 0; row < this.size; row++)
		{
			for(int col = 0; col < this.size; col++)
			{
				this.set(row, col, new ArrayList<GraphLink>());
			}
		}
	}
	
	public NodeLinkMatrix() {
		super(true);
		this.links = new ArrayList<GraphLink>();
	}
	
	public ArrayList<GraphNode> getNeighbours(GraphNode value)
	{
		ArrayList<GraphNode> row = new ArrayList<GraphNode>();
		int rowIndex = this.getIndex(value);
		
		for(int i = 0; i < this.size; i++)
		{
			if(this.get(rowIndex, i).size() > 0)
			{
				row.add(this.keys.get(i));
			}
		}
		return row;
	}
	
	public void addKey(GraphNode key)
	{
		super.addKey(key);
		
		for(int row = 0; row < this.size; row++)
		{
			if(row == this.size -1)
			{
				for(int col = 0; col < this.size; col++)
				{
					this.set(row, col, new ArrayList<GraphLink>());
				}
			}
			else
			{
				this.set(row, this.size-1, new ArrayList<GraphLink>());
			}
		}
	}
	
	public void add(GraphLink link)
	{
		int row = this.getIndex( link.getFirstNode());
		int col = this.getIndex( link.getSecondNode());
		ArrayList<GraphLink> currentLinks = this.get(row, col);
		currentLinks.add(link);
		links.add(link);
	}

	public boolean hasLink(GraphNode firstNode, GraphNode secondNode)
	{
		if(!this.keys.contains(firstNode))
		{
			return false;
		}
		if(!this.keys.contains(secondNode))
		{
			return false;
		}
		return (this.get(firstNode, secondNode).size() > 0);
	}
	
	@Override	
	public String[] getKeysArrayString()
	{
		String[] strs = new String[this.size];
		int i = 0;
		for(Iterator<GraphNode> it = this.keys.iterator(); it.hasNext();)
		{
			strs[i++] = ""+it.next().getName();
		}
		return strs;
	}
	
	public GraphDoor getDoorNode(DoorModel model) {
		for(Iterator<GraphNode> it = this.keys.iterator(); it.hasNext();)
		{
			GraphNode node = it.next();
			
			if(node.getClass() == GraphDoor.class)
			{
				GraphDoor door = (GraphDoor)node;
				if(door.getDoorModel() == model)
				{
					return door;
				}
			}
		}
		return null;
	}
	
	public DijkstraResult getShortestPath(GraphNode beginNode, GraphNode endNode)
	{
		float length = 0;
		SquareMatrix<GraphNode, Double> resultMatrix = new SquareMatrix<GraphNode, Double>(false);
		for(Iterator<GraphNode> it = this.getKeys().iterator(); it.hasNext();)
		{
			resultMatrix.addKey(it.next());
		}
		Stack<GraphNode> resultStack = new Stack<GraphNode>();
		
		Hashtable<GraphNode, Double> nodesValue = new Hashtable<GraphNode, Double>();
		Hashtable<GraphNode, GraphNode> nodesPrevious = new Hashtable<GraphNode, GraphNode>();
		
		ArrayList<GraphNode> alreadyCoveredNodes = new ArrayList<GraphNode>();
		
		Stack<GraphNode> currentStack = new Stack<GraphNode>();
		
		currentStack.add(beginNode);		
		alreadyCoveredNodes.add(beginNode);
		nodesValue.put(beginNode, 0.0);

		while(!currentStack.isEmpty())
		{
			GraphNode node = null;
			for(Iterator<GraphNode> it = currentStack.iterator(); it.hasNext();)
			{
				GraphNode currentNode = it.next();
				if(node == null || nodesValue.get(currentNode) < nodesValue.get(node))
				{
					node = currentNode;
				}
			}
			ArrayList<GraphNode> neighbours = this.getNeighbours(node);
			Collections.shuffle(neighbours);
			
			for(Iterator<GraphNode> itNeighbourgs = neighbours.iterator(); itNeighbourgs.hasNext();)
			{
				GraphNode neighbour = itNeighbourgs.next();
				double nodeValue = nodesValue.get(node);
				ArrayList<GraphLink> links = this.get(node, neighbour);
				Collections.shuffle(links);
				double currentLength = 999999999;
				for(Iterator<GraphLink> itLink = links.iterator(); itLink.hasNext();)
				{
					GraphLink link = itLink.next();
					
					if(link.getLength() < currentLength )
					{
						currentLength = link.getLength() ;
					}
				}
				resultMatrix.set(node, neighbour, currentLength);

				if(!nodesValue.containsKey(neighbour) )
				{
					nodesValue.put(neighbour, nodeValue + currentLength);
					nodesPrevious.put(neighbour,  node);
				}
				else
				{
					double currentValue = nodesValue.get(neighbour);
					
					if(currentValue >= nodeValue + currentLength)
					{
						nodesValue.put(neighbour, nodeValue+currentLength);
						nodesPrevious.put(neighbour,  node);
					}
				}
			}	
			alreadyCoveredNodes.add(node);
			currentStack.addAll(neighbours);
			currentStack.removeAll(alreadyCoveredNodes);
		}
		
		resultStack.push(endNode);
		GraphNode pathNode = nodesPrevious.get(endNode);
		GraphNode oldNode = null;
		while(pathNode != null)
		{
			resultStack.push(pathNode);
			if(oldNode == null)
			{
				ArrayList<GraphLink> links = this.get(endNode, pathNode);
				Collections.shuffle(links);
				length+= links.get(0).getLength();
				
				oldNode = pathNode;
				pathNode = nodesPrevious.get(pathNode);
			}
			else
			{

				ArrayList<GraphLink> links = this.get(oldNode, pathNode);
				Collections.shuffle(links);
				oldNode = pathNode;
				pathNode = nodesPrevious.get(pathNode);
				
				length+= links.get(0).getLength();
			}
		}
		
		Collections.reverse(resultStack);
		DijkstraResult result = new DijkstraResult(resultStack, length);
		return result;
	}

	public void save(String string) throws IOException {
		String strFile =  "./src/data/" + string;
		
		File file = new File(strFile);
		if(!file.exists())
		{
			file.createNewFile();
		}
		FileWriter fw = new FileWriter(file);
		BufferedWriter output = new BufferedWriter(fw);

		String currentLine = "      ";
		
		for(Iterator<GraphNode> it = this.keys.iterator(); it.hasNext();)
		{
			currentLine += " |  " +  it.next();
		}
		currentLine += "\n";
		output.write(currentLine);
		output.flush();
		currentLine = "";
		
		for(int row = 0; row < this.size; row++)
		{	
			currentLine += this.keys.get(row);
			
			for(int col = 0; col < this.size; col++)
			{
				currentLine += "   |   " +  this.get(row, col);
			}	
			output.write(currentLine);
			output.flush();
			currentLine = "";
			currentLine += "\n";
		}
		output.close();
	}
}
